package com.winit.core.host.shell;

import com.winit.common.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.*;

/**
 * Created by wangjian on 16/6/22.
 */
public class Sheller implements Runnable{

    private static final Logger logger = LoggerFactory.getLogger(Sheller.class);
    //当前sheller启动的linux进程id
    private List<String> pids = new ArrayList<>();

    public List<Listener> listenerList = new ArrayList<>();

    //日志文件路径
    private String logFilePath;

    //执行命令 例:sh beetle.sh
    private String cmd;
    //执行参数 多个参数空格隔开
    private String params;

    private String shellId;
    private String ip;
    private String sshUser;
    private String sshPass;
    private String sshPort;
    private String groupId;
    //sheller运行的最大时间,提供查询剩余时间.单位:秒
    private Integer expectedRunTime = 0;
    private long startTime = 0L;

    private Boolean shellStop = Boolean.FALSE;

    private String desc;//自动退出时脚本返回日志
    private String status;//自动退出状态

    private Process process = null;
    private BufferedInputStream in = null;
    private BufferedReader br = null;

    public Sheller(){}

    public Sheller(String groupId,String cmd,String params,String shellId,String logFilePath,Integer runTime,Listener listener){
        this.cmd = cmd;
        this.groupId = groupId;
        this.params = params;
        this.shellId = shellId;
        this.expectedRunTime = runTime;
        this.logFilePath = logFilePath;
        this.startTime = new Date().getTime();
        this.listenerList.add(listener);
    }

    public Sheller(String groupId,String cmd,String params,String shellId,String logFilePath,Integer runTime,
                   String ip,String sshPort,String sshUser,String sshPass,Listener listener){
        this.groupId = groupId;
        this.cmd = cmd;
        this.params = params;
        this.shellId = shellId;
        this.expectedRunTime = runTime;
        this.ip = ip;
        this.sshPort = sshPort;
        this.sshUser = sshUser;
        this.sshPass = sshPass;
        this.logFilePath = logFilePath;
        this.startTime = new Date().getTime();
        this.listenerList.add(listener);
    }

    public void addListener(Listener listener){
        this.listenerList.add(listener);
    }

    @Override
    public void run() {
        this.writeLog("Sheller线程启动,cmd="+this.cmd+",params="+this.params+",shellId="+this.shellId+",runTime="+this.expectedRunTime
                +",logFilePath="+this.logFilePath+",ip="+this.ip+",sshPort="+this.sshPort);

        if(!this.validate()){
            this.onClose();
            this.writeLog("Sheller线程关闭,参数校验失败,cmd="+this.cmd+",params="+this.params+",shellId="+this.shellId+",runTime="+this.expectedRunTime
                    +",logFilePath="+this.logFilePath+",ip="+this.ip+",sshPort="+this.sshPort+",desc="+this.desc);
            return;
        }

        String[] param = this.createParams();

        try {

            if(!StringUtils.isEmpty(this.ip)){
                this.validateSSH();
            }

            this.doShell(param);


        }catch (Exception e) {
            e.printStackTrace();
            logger.error("Sheller线程异常,cmd="+this.cmd+",shellId="+this.shellId+",logFilePath="+this.logFilePath,e);
            FileUtil.writeLog(this.logFilePath,"Sheller线程异常,cmd="+this.cmd+",shellId="+this.shellId+",logFilePath="+this.logFilePath+",e:"+e.getMessage());
        }finally {
            this.stopPids();
            this.stopProcess();
            this.onClose();
        }

        this.writeLog("Sheller线程关闭,cmd="+this.cmd+",params="+this.params+",shellId="+this.shellId+",runTime="+this.expectedRunTime
                +",logFilePath="+this.logFilePath+",desc="+this.desc);
    }

    private boolean validate(){

        if(StringUtils.isEmpty(this.cmd) || StringUtils.isEmpty(this.shellId)){
            this.writeLog("Sheller线程启动,参数存在空值,cmd="+this.cmd+",shellId="+this.shellId);
            this.desc = "参数存在空值";
            return false;
        }

        if( (StringUtils.isEmpty(this.ip) && !StringUtils.isEmpty(this.sshPort))
                || (!StringUtils.isEmpty(this.ip) && StringUtils.isEmpty(this.sshPort))){
            this.writeLog("Sheller线程启动,参数错误,ip="+this.ip+",sshPort="+this.sshPort);
            this.desc = "参数错误";
            return false;
        }

        if( !StringUtils.isEmpty(this.sshUser) && StringUtils.isEmpty(this.sshPass) ){
            this.writeLog("Sheller线程启动,参数错误,sshUser="+this.sshUser+",sshPass="+this.sshPass);
            this.desc = "参数错误";
            return false;
        }

        if(!StringUtils.isEmpty(this.ip)){
            if(!SshUtil.validateIp(this.ip)){
                this.writeLog("Sheller线程启动,远程终端ip不可用,ip="+this.ip+",sshPort="+this.sshPort);
                this.desc = "远程终端ip不可用";
                return false;
            }
        }
        return true;
    }

    private void doShell(String[] param) throws Exception{
        this.process = Runtime.getRuntime().exec(param);
        this.in = new BufferedInputStream(this.process.getInputStream());
        this.br = new BufferedReader(new InputStreamReader(this.in));
        String line;
        while ( !this.shellStop && (line=this.br.readLine()) != null) {
            this.desc = line;
            FileUtil.writeLog(this.logFilePath,line);
            if(line.startsWith("[[PID]]")){
                this.pids.add(line.replace("[[PID]]",""));
                this.postPid(line.replace("[[PID]]",""));
            }else if(line.startsWith("[[ERROR]]")){
                this.stopSheller();
                this.status = ShellerContainer.ShellerStatus.SHELLER_ERROR.getDesc();
            }else if(line.startsWith("[[SUCCESS]]")){
                this.stopSheller();
                this.status = ShellerContainer.ShellerStatus.SHELLER_SUCCESS.getDesc();
            }
        }
    }

    private void validateSSH(){
        String[] param = new String[6];
        param[0] = "bash";
        param[1] = Constants.SHELL_PATH+"/shell/terminal/beetle_terminal_validate_ssh.sh";
        param[2] = this.ip;
        param[3] = Constants.SSH_LOGIN_USER;
        param[4] = this.sshPort;
        param[5] = Constants.SHELL_PATH;

        ShellRunUtil.runShell(param);
    }

    public void stopSheller(){
        this.writeLog("Sheller退出,cmd="+this.cmd+",params="+this.params+",shellId="+this.shellId);
        this.shellStop = Boolean.TRUE;
//        this.stopPids();
        this.stopProcess();
//        this.onClose();
    }

    public void timeOutToStopSheller(){
        this.writeLog("Sheller超时退出,cmd="+this.cmd+",params="+this.params+",shellId="+this.shellId);
        this.status = ShellerContainer.ShellerStatus.SHELLER_TIME_OUT.getDesc();
        this.stopSheller();
    }

    private void stopPids(){
        this.writeLog("停止线程相关pid,cmd="+this.cmd+",params="+this.params+",shellId="+this.shellId);
        //kill pid
        if(this.pids.size() == 0){
            this.stopPidByName();
            return;
        }
        for (String pid : this.pids){
            this.killPid(pid);
        }
        this.pids.clear();
    }

    private void stopPidByName(){
        StringBuffer command = new StringBuffer();
        String str = "";
        if(StringUtils.isEmpty(this.ip)){
            command.append("ps -ef | grep -w '").append(this.cmd).append("' | grep -v grep | awk '{print $2}'");
            str = ShellRunUtil.runShell(this.getRunShellParam(command.toString()));
            this.writeLog("根据cmd停止本地进程,查询进程号,command="+command.toString()+",str="+str);

        }else{
            command.append("ps -ef | grep -w '").append(this.cmd).append("' | grep -v grep");
            String param = this.getSSHParam(command.toString()) + " | awk '{print $2}'";
            str = ShellRunUtil.runShell(this.getRunShellParam(param));
            this.writeLog("根据cmd停止远程进程,查询进程号,command="+command.toString()+",str="+str);
        }
        if(StringUtils.isEmpty(str) || ",".equals(str)){
            return;
        }
        String[] strings = str.split(",");
        for(String pid:strings){
            this.killPid(pid);
        }
    }

    private void killPid(String pid){
        if(StringUtils.isEmpty(pid)){
            return;
        }
        StringBuffer command = new StringBuffer();

        if(!StringUtils.isEmpty(this.ip)){
            command.append(this.getSSHParam("kill -9 "+pid));
        }else{
            command.append("kill -9 ").append(pid);
        }
        String str = ShellRunUtil.runShell(this.getRunShellParam(command.toString()));
        this.writeLog("停止线程相关pid,cmd="+this.cmd+",params="+this.params+",shellId="+this.shellId+",pid="+pid+",rtn="+str);
    }


    private void stopProcess(){
        try {
            if(this.process != null){
                this.process.destroy();
            }
        } catch (Exception e) {
            e.printStackTrace();
            this.writeLog("停止线程,关闭Process异常,cmd="+this.cmd+",shellId="+this.shellId);
        }

        try {
            if(this.br != null){
                this.br.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
            this.writeLog("停止线程,关闭BufferedReader异常,cmd="+this.cmd+",shellId="+this.shellId);
        }

        try {
            if(this.in != null){
                this.in.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
            this.writeLog("停止线程,关闭BufferedInputStream异常,cmd="+this.cmd+",shellId="+this.shellId);
        }

    }

    public String getLogFilePath(){
        return this.logFilePath;
    }

    public String getCmd(){
        return this.cmd;
    }

    public Integer getShellerRunTime(){
        Date now = new Date();
        long run = now.getTime() - this.startTime;
        return Math.round(new Long(run).intValue()/1000);
    }

    public Integer getShellerLeftTime(){
        if(this.expectedRunTime == 0){
            return 0;
        }
        Integer run = this.getShellerRunTime();
        return this.expectedRunTime - run;
    }

    private void writeLog(String log){
        logger.info(log);
        FileUtil.writeLog(this.logFilePath,log);
    }

    private String[] createParams(){
//        StringBuffer command = new StringBuffer();
//        command.append(this.cmd);
//        if(!StringUtils.isEmpty(this.params)){
//            command.append(" ").append(this.params);
//        }
//        if(StringUtils.isEmpty(this.ip)) {//本地执行
//            return this.getRunShellParam(command.toString());
//        }else {//远程调用
//            return this.getRunShellParam(this.getSSHParam(command.toString()));
//        }




        if(StringUtils.isEmpty(this.ip)){//本地执行
            String[] cmds = this.cmd.split(" ");
            Integer size = cmds.length;
            String[] param = new String[size+1];
            for(int i=0;i<size;i++){
                param[i] = cmds[i];
            }
            param[size] = this.params;
            return param;
        }else{//远程调用
            String paramStr = this.getSSHParam("");
            String[] pas = paramStr.split(" ");
            Integer size = pas.length;
            String[] param = new String[size+1];
            for(int i=0;i<size;i++){
                param[i] = pas[i];
            }
            param[size] = this.cmd+" "+this.params;
            return param;
        }
    }

    public interface Listener {

        void postPid(String shellId,String pid);

        void onClose(String shellId,String desc,String status,String groupId);

    }

    public void postPid(String pid){
        if(this.listenerList.size() == 0){
            return;
        }
        for(Listener listener:this.listenerList){
            listener.postPid(this.shellId,pid);
        }
    }

    public void onClose(){
        this.writeLog("Sheller关闭,调用onClose,shellId="+this.shellId+",desc="+this.desc+",status="+this.status+",groupId="+this.groupId);
        if(this.listenerList.size() == 0){
            return;
        }
        for(Listener listener:this.listenerList){
            listener.onClose(this.shellId,this.desc,this.status,this.groupId);
        }
    }

    private String[] getRunShellParam(String command){
        String[] param = new String[3];
        param[0] = "bash";
        param[1] = Constants.SHELL_PATH+"shell/beetle_doshell.sh";
        param[2] = command;
        return param;
    }

    private String getSSHParam(String command){
        StringBuffer param = new StringBuffer();

        if(!StringUtils.isEmpty(this.sshUser)){
            param.append("sshpass -p ").append(this.sshPass).append(" ssh").append(" -p ").append(this.sshPort).append(" ")
                    .append(this.sshUser).append("@").append(this.ip);
        }else{
            param.append("ssh -i /home/").append(ConfigUtil.getValue("ssh_login_user")).append("/").append(ConfigUtil.getValue("ssh_key_file"))
                    .append(" -p ").append(this.sshPort).append(" ").append(ConfigUtil.getValue("ssh_login_user")).append("@").append(this.ip);
        }
        if(!StringUtils.isEmpty(command)) {
            param.append(" \"").append(command).append("\"");
        }
        return param.toString();
    }

    public class ShellerObject{
        private String cmd;
        private String params;
        private String shellId;
        private Integer runTime;
        private boolean isSingle;
        private Sheller.Listener listener;

        public String getCmd() {
            return cmd;
        }

        public void setCmd(String cmd) {
            this.cmd = cmd;
        }

        public String getParams() {
            return params;
        }

        public void setParams(String params) {
            this.params = params;
        }

        public String getShellId() {
            return shellId;
        }

        public void setShellId(String shellId) {
            this.shellId = shellId;
        }

        public Integer getRunTime() {
            return runTime;
        }

        public void setRunTime(Integer runTime) {
            this.runTime = runTime;
        }

        public Sheller.Listener getListener() {
            return listener;
        }

        public void setListener(Sheller.Listener listener) {
            this.listener = listener;
        }

        public boolean isSingle() {
            return isSingle;
        }

        public void setSingle(boolean single) {
            isSingle = single;
        }
    }
}
